// Texture.h
//
#ifndef TEXTURE_ONCE
#define TEXTURE_ONCE

#include "Framework_Exports.h"


#include "OpenGL.h"
#include <fstream>
#include <memory>

namespace OpenGL {

// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class TextureUtility
{
public:
	// ------------------------------------------------------------------------------------------------
	static int log2(unsigned int x)
	{
		int y = 0;
		while (x >>= 1) ++y;
		return y;
	}


	// ------------------------------------------------------------------------------------------------
	static bool isPowerOfTwo(unsigned int x)
	{
		return ((x & (x - 1)) == 0);
	}

	// ------------------------------------------------------------------------------------------------
	static unsigned int nextPowOfTwo(unsigned int x)
	{
		x--;
		x |= x >> 1;  // handle  2 bit numbers
		x |= x >> 2;  // handle  4 bit numbers
		x |= x >> 4;  // handle  8 bit numbers
		x |= x >> 8;  // handle 16 bit numbers
		x |= x >> 16; // handle 32 bit numbers
#if 0	
		// @TODO: Enable only for 64bit compilers
		x |= x >> 32; // handle 64 bit numbers
#endif

		x++;

		return x;
	}

	// ----------------------------------------------------------------------------------------------
	static 	void dumpTGA(std::string targaFilename, unsigned char *pixels, short numChannels, short w, short h)
	{
		//typedef struct {
		//   //char  idlength;
		//   //char  colourmaptype;
		//   //char  datatypecode;
		//   //short int colourmaporigin;
		//   //short int colourmaplength;
		//   //char  colourmapdepth;
		//   //short int x_origin;
		//   //short int y_origin;
		//   //short width;
		//   //short height;
		//   //char  bitsperpixel;
		//   //char  imagedescriptor;
		//	GLubyte Header[12]; //12 bytes
		//} TGA_HEADER;

		const char cTgaHeader[12] = { 0, 0, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0 };

		std::ofstream file;

		// Open the file for output
		file.open(targaFilename.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); 

		if (!file)
			return;

		// swap RGB -> BGR
		char *pxl = new char[w*h*numChannels];
		memcpy(pxl,pixels,sizeof(char)*w*h*numChannels);
		for(int i=0; i<w*h*numChannels; i+=numChannels)
		{
			pxl[i + 0] = pxl[i + 0] ^ pxl[i + 2];
			pxl[i + 2] = pxl[i + 0] ^ pxl[i + 2];
			pxl[i + 0] = pxl[i + 0] ^ pxl[i + 2];
		}

		// Create some temporaries
		char bitdepth = numChannels * 8;
		char bituse = 0;

		// Write out all targa image data
		file.write(cTgaHeader, 12 * sizeof(char));

		file.write((char*)&w, sizeof(short));
		file.write((char*)&h, sizeof(short));
		file.write(&bitdepth, sizeof(char));
		file.write(&bituse, sizeof(char));

		file.write((char*)pxl, w * h * numChannels * sizeof(char));

		// Close file again
		file.close();
		delete [] pxl;
	}

};

// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class Texture;
class Texture1D;
class Texture2D;

typedef std::shared_ptr<Texture>   TexturePtr;
typedef std::shared_ptr<Texture1D> Texture1DPtr;
typedef std::shared_ptr<Texture2D> Texture2DPtr;

class Texture : public std::enable_shared_from_this<Texture>
{
protected:
	GLuint	_textureID;
	GLenum  _dataFormat;
	GLenum  _dataType;
	bool    _valid;
	mutable int     _unit;

public:
	Texture() : _textureID(0),_valid(false),_dataFormat(0),_dataType(0), _unit(-1) {}
	virtual ~Texture() { destroy(); }

	// Create empty Texture
	virtual void	create()
	{
		destroy();
		glGenTextures(1, &_textureID);
		glBindTexture(target(),_textureID);
	}

	// Delete Texture
	virtual void	destroy()
	{
		if(_textureID != 0)
		{
			glDeleteTextures(1, &_textureID);
			_textureID = 0;
		}
		_valid = false;
	}

	// Custom methods
	virtual void	setParameter(GLenum pname, GLint i)		const 	{ glTexParameteri(target(), pname, i);   }
	virtual void	setParameter(GLenum pname, GLfloat f)	const 	{ glTexParameterf(target(), pname, f);   }
	virtual void	setParameter(GLenum pname, GLint *ip)	const 	{ glTexParameteriv(target(), pname, ip); }
	virtual void	setParameter(GLenum pname, GLfloat *fp)	const 	{ glTexParameterfv(target(), pname, fp); }

	
	virtual void    dump( const std::string &filename ) {}

	virtual void	bind( unsigned int unit = 0 )		const { _unit = unit; glActiveTexture( GL_TEXTURE0+_unit ); glBindTexture(target(),_textureID); glActiveTexture( GL_TEXTURE0 ); }
	virtual void	unbind()							const { glActiveTexture( GL_TEXTURE0+_unit ); glBindTexture(target(),0); glActiveTexture( GL_TEXTURE0 ); _unit = -1; }
	
	virtual bool	isValid()	const { return _valid;   }
	virtual bool    isBound()   const { return _unit>=0; }

	virtual unsigned int unit() const { return _unit;    }
	virtual int     width()     const { return -1; }
	virtual int     height()    const { return -1; }

	unsigned int	id()	     const { return _textureID;}
	GLenum          dataFormat() const { return _dataFormat; }
	GLenum          dataType()   const { return _dataType; }

	virtual GLenum	target() const = 0;
};
/////////////////////////////////////////////////////////////////////////////



////////////////////////////////////
// Texture 1D Class
class Texture1D : public Texture
{
protected:
	int texWidth;
	int texChannels;

	void create(
		int numChannels,
		GLenum format,
		GLenum type,
		bool mipmaps,
		bool border,
		int width,
		int height,
		void *pixels)
	{}

public:	
	static Texture1DPtr New() { return std::make_shared<Texture1D>(); }
	static Texture1DPtr New(int numChannels,GLenum format,GLenum type,bool mipmaps,bool border,int width,void *pixels)
	{ Texture1DPtr t = std::make_shared<Texture1D>(); t->create(numChannels,format,type,mipmaps,border,width,pixels); return t;}

	Texture1D() : Texture()	{texWidth = 0;}

	GLenum	target()	const {return GL_TEXTURE_1D;}

	void create(
		int numChannels,
		GLenum format,
		GLenum type,
		bool mipmaps,
		bool border,
		int width,
		void *pixels)
	{
		Texture::create();

		texWidth    = width;
		texChannels = numChannels;
		_dataFormat = format;
		_dataType   = type;

		if( mipmaps )	gluBuild1DMipmaps(GL_TEXTURE_1D,numChannels, texWidth, format, type, pixels);
		else			glTexImage1D(GL_TEXTURE_1D, 0, format, width, border, format, type, pixels);

		_valid = glValidate;
	}

	void clear()
	{
		glBindTexture(GL_TEXTURE_1D, _textureID);
		
		unsigned char *pxl = new unsigned char[texWidth*1*texChannels];
		memset(pxl,0,texWidth*1*texChannels);
		glTexSubImage1D(GL_TEXTURE_1D, 0,0, texWidth, _dataFormat, _dataType,pxl);
		delete [] pxl;
		
		_valid = glValidate;
	}

	virtual void dump( const std::string &filename )
	{
		bind();

		unsigned char *data = new unsigned char[texWidth*1*4];
		glGetTexImage( GL_TEXTURE_1D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );

		TextureUtility::dumpTGA( filename, data, 4, texWidth, 1 );

		delete [] data;
		unbind();	
	}

	virtual int     width()     const { return texWidth; }
	virtual int     height()    const { return 1; }
};


//////////////////////////////////////
// Texture 2D Class
class Texture2D : public Texture
{
protected:
	int texWidth;
	int texHeight;
	int texChannels;
	float u,v;

	// -----------------------------------------------------------------------------------------------
	void setDefaultFilterParameters()
	{
		if( !_valid )
			return;
		
		setParameter( GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		setParameter( GL_TEXTURE_MAG_FILTER, GL_NEAREST );
		setParameter( GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		setParameter( GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	}

public:	
	static Texture2DPtr New() { return std::make_shared<Texture2D>(); }
	static Texture2DPtr New(int numChannels,GLenum format,GLenum type,bool mipmaps, bool border,int width,int height,void *pixels)
	{ Texture2DPtr t = std::make_shared<Texture2D>(); t->create(numChannels,format,type,border,mipmaps,width,height,pixels); return t;}

	Texture2D() : Texture(), texWidth(0), texHeight(0), u(1.f), v(1.f) {}

	float getMaxU()		const {return u;}
	float getMaxV()		const {return v;}
	virtual int     width()     const { return texWidth; }
	virtual int     height()    const { return texHeight; }
	GLenum	target()	const {return GL_TEXTURE_2D;}

	// -----------------------------------------------------------------------------------------------
	void create(
		int numChannels,
		GLenum format,
		GLenum type,
		bool mipmaps,
		bool border,
		int width,
		int height,
		void *pixels)
	{
		Texture::create();
		
		texWidth	= width;
		texHeight	= height;
		texChannels = numChannels;
		_dataFormat = format;
		_dataType   = type;

		if( mipmaps )	gluBuild2DMipmaps(GL_TEXTURE_2D,numChannels, texWidth, texHeight, /*  */  _dataFormat, _dataType, pixels);
		else			glTexImage2D(GL_TEXTURE_2D, 0,  numChannels, texWidth, texHeight, border, _dataFormat, _dataType, pixels);

		_valid = glValidate;

		if( _valid )
			setDefaultFilterParameters();
	}

	// -----------------------------------------------------------------------------------------------
	void createFromFrameBuffer( int width, int height )
	{
		// copy the framebuffer pixels to a texture
		Texture::create();

		texWidth	= width;
		texHeight	= height;
		texChannels = 4;
		_dataFormat = GL_RGBA8;
		_dataType   = GL_UNSIGNED_BYTE;

		glCopyTexImage2D(GL_TEXTURE_2D, 0, _dataFormat, 0, 0, texWidth, texHeight, 0 );
		_valid = glValidate;

		setDefaultFilterParameters();
	}

	// -----------------------------------------------------------------------------------------------
	void update(int xOffset,
		int yOffset,
		int width,
		int height,
		void *pixels)
	{
		bind();
		glTexSubImage2D(GL_TEXTURE_2D,0, xOffset, yOffset, width, height, _dataFormat, _dataType, pixels);
		_valid = glValidate;
		unbind();
	}

	// -----------------------------------------------------------------------------------------------
	void clear()
	{
		glBindTexture(GL_TEXTURE_2D, _textureID);
		
		unsigned char *pxl = new unsigned char[texWidth*texHeight*texChannels];
		memset(pxl,0,texWidth*texHeight*texChannels);
		glTexSubImage2D(GL_TEXTURE_2D, 0,0,0, texWidth, texHeight, _dataFormat, _dataType,pxl);
		delete [] pxl;

		u      = 1.f;
		v      = 1.f;
		_valid = glValidate;
	}

	//void setMaxU(float U) { u = U; }
	void setMaxWidth(int w)   { u = (float)w/(float)texWidth; }
	//void setMaxV(float V) { v = V; }
	void setMaxHeight(int h)   { v = (float)h/(float)texHeight; }

	// -----------------------------------------------------------------------------------------------
	virtual void dump( const std::string &filename )
	{
		bind();

		unsigned char *data = new unsigned char[texWidth*texHeight*4];
		glGetTexImage( GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );

		TextureUtility::dumpTGA( filename, data, 4, texWidth, texHeight );

		delete [] data;
		unbind();
	}
};

}



#endif